/*
 * SecurityStorage.java
 *
 * Created on Sep 19, 2007, 5:17:06 PM
 *
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.glossitope.container.security.permissions;

import org.glossitope.container.security.*;
import java.beans.Encoder;
import java.beans.Expression;
import java.beans.PersistenceDelegate;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.InetAddress;
import java.net.SocketPermission;
import java.net.UnknownHostException;
import java.security.CodeSource;
import java.security.Permission;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.glossitope.container.Environment;
import org.glossitope.container.util.StringUtils;
import org.joshy.util.u;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 * A singleton which manages the storage of security permissions
 *
 * notes
 *
 * currently uses a list of perms. can I make that be a set instead?
 * @author joshy
 */
public class SecurityStorage {

    private static SecurityStorage ss;
    private static String STORAGE_FILE = "security.xml";
    private static boolean autosave = true;

    public static synchronized SecurityStorage getInstance() {
        if (ss == null) {
            ss = new SecurityStorage();
        }
        return ss;
    }
    //UUID => list of perms
    private Map<String, List<GPerm>> permsMap;
    private boolean usenewencoding = true;

    private SecurityStorage() {
        //System.out.println("SS: security storage created");
        permsMap = new HashMap<String, List<GPerm>>();
        //System.out.println("my classloader = " + this.getClass().getClassLoader());
        load();
    }

    private List<GPerm> getPerms(String uuid) {
        //u.p("SS: getting perms for: " + uuid);
        if (!permsMap.containsKey(uuid)) {
            permsMap.put(uuid.intern(), new ArrayList<GPerm>());
        }
        return permsMap.get(uuid);
    }

    private static String getUUID(CodeSource src) {
        String externUrl = src.getLocation().toExternalForm();
        String uuid = StringUtils.substring(externUrl, "glossitope/(.+?)/");
        return uuid;
    }

    public synchronized void grantPermission(CodeSource codeSource, Permission perm) {
//        u.p("SS: granting " + perm + " for " + codeSource);
        String uuid = getUUID(codeSource);
        List<GPerm> perms = getPerms(uuid);
        GPerm gp = new GPerm(uuid, perm.getName(), GPerm.Type.ALLOW_TEMP);
        if(perm instanceof SocketPermission) {
            gp.setPermType("http");
        }
        perms.add(gp);
        save();
    }

    public synchronized void grantPermissionPermanently(String uuid, GPerm perm) {
        //u.p("SS: granting permanently: " + perm.value + " for " + uuid);
        List<GPerm> perms = getPerms(uuid);
        GPerm p = new GPerm(perm.code, perm.value, GPerm.Type.ALLOW_PERM);
        p.setPermType(perm.getPermType());
        perms.add(p);
        save();
    }

    public synchronized void grantPermissionPermanently(CodeSource codeSource, Permission perm) {
        //u.p("SS: granting permanently: " + perm + " for " + codeSource);
        String uuid = getUUID(codeSource);
        List<GPerm> perms = getPerms(uuid);
        GPerm gp = new GPerm(uuid, perm.getName(), GPerm.Type.ALLOW_PERM);
        if(perm instanceof SocketPermission) {
            gp.setPermType("http");
        }
        perms.add(gp);
        save();
    }

    public synchronized void denyPermissionPermanently(CodeSource codeSource, Permission perm) {
        String uuid = getUUID(codeSource);
        List<GPerm> perms = getPerms(uuid);
        GPerm gp = new GPerm(uuid, perm.getName(), GPerm.Type.DENY_PERM);
        perms.add(gp);
        if(perm instanceof SocketPermission) {
            gp.setPermType("http");
        }
        save();
    }

    
    
    public synchronized boolean isGranted(String uuid, Permission perm) {
        //u.p("SS: checking for granted: " + perm + " on " + uuid);
        //u.p("existing perm = " + perm);
        List<GPerm> perms = getPerms(uuid);
        for (GPerm p : perms) {
            if (p == null) {
                continue;
            }
            //u.p("type = " + p.permType);

            //handle http types first
            if ("http".equals(p.permType)) {
                try {
                    //u.p("doing a new type of socket test");
                    //String host = "www.weather.gov";
                    //u.p("value = " + p.value);
                    String host = p.value;
                    final List<Permission> permlist = new ArrayList<Permission>();
                    permlist.add(new SocketPermission(host, "connect,resolve"));
                    permlist.add(new SocketPermission(bytesToIPAddress(InetAddress.getByName(host)) + ":80", "connect,resolve"));
                    //u.p("perm list completed");
                    for (Permission per : permlist) {
                        //u.p("testing " + per + " against " + perm);
                        if (per.implies(perm)) {
                            return true;
                        }
                    }
                } catch (UnknownHostException ex) {
                    Logger.getLogger(SecurityStorage.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

            String value = perm.getName();
            //handle all other types
            if (p.value.equals(value) && (p.type == GPerm.Type.ALLOW_PERM || p.type == GPerm.Type.ALLOW_TEMP)) {
                //u.p("SS: it has been granted");
                return true;
            }
        }
        //u.p("SS: it was not granted");
        return false;
    }
    
    public boolean isGranted(String uuid, String type, Object[] params) {
        if("http".equals(type)) {
            return isGranted(uuid, new SocketPermission((String)params[0],"resolve"));
        }
        return false;
    }

    private void save() {
        if (!autosave) {
            return;
        }
        //u.p("SS: saving");
        try {
            if (usenewencoding) {
                FileOutputStream out = new FileOutputStream(Environment.HOME + "/" + STORAGE_FILE);
                PrintStream pout = new PrintStream(out);
                pout.println("<permissions_set>");
                for(String uuid : permsMap.keySet()) {
                    pout.println("   <desklet uuid='"+uuid+"'>");
                    for(GPerm p : getPerms(uuid)) {
                        pout.print("      <permission ");
                        pout.print("access='");
                        switch(p.getType()) {
                            case ALLOW_PERM: pout.print("allow_permanent"); break;
                            case ALLOW_TEMP: pout.print("allow_temporary"); break;
                            case DENY_PERM: pout.print("deny_permanent"); break;
                        }
                        pout.print("'");
                        pout.print(" type='"+p.getPermType()+"'");
                        pout.print(" value='"+p.getValue()+"'");
                        pout.println("/>");
                    }
                    pout.println("   </desklet>");
                }
                pout.println("</permissions_set>");
                pout.flush();
                pout.close();
            } else {
                XMLEncoder enc = new XMLEncoder(new FileOutputStream(Environment.HOME + "/" + STORAGE_FILE));
                enc.setPersistenceDelegate(GPerm.Type.class, new TypeSafeEnumPersistenceDelegate());
                enc.writeObject(permsMap);
                enc.flush();
                enc.close();
            }
            //u.p("SS: saved!");
        } catch (IOException ex) {
            u.p(ex);
        }
    }

    private void load() {
        if(usenewencoding) {
            try {
                permsMap = new HashMap<String, List<GPerm>>();
                XMLReader xr = XMLReaderFactory.createXMLReader();
                xr.setContentHandler(new DefaultHandler() {
                    String currentUUID;
                    @Override
                    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
                        if("desklet".equals(localName)) {
                            String uuid = attributes.getValue("uuid");
                            permsMap.put(uuid, new ArrayList<GPerm>());
                            currentUUID = uuid;
                        }
                        if("permission".equals(localName)) {
                            GPerm.Type type = GPerm.Type.ALLOW_TEMP;
                            if("allow_permanent".equals(attributes.getValue("access"))) {
                                type = GPerm.Type.ALLOW_PERM;
                            }
                            if("deny_permanent".equals(attributes.getValue("access"))) {
                                type = GPerm.Type.DENY_PERM;
                            }
                            GPerm gp = new GPerm(currentUUID, 
                                    attributes.getValue("type"),
                                    attributes.getValue("value"),
                                    type);
                            permsMap.get(currentUUID.intern()).add(
                                    gp
                                    );
                            //u.p("loaded a permission: " + currentUUID + 
                            //        " - " + gp.code + " " + gp.permType + " " + gp.getValue());
                        }
                    }
                    
                });
                xr.parse(new InputSource(new FileInputStream(Environment.HOME + "/" + STORAGE_FILE)));
                return;
            } catch (FileNotFoundException fnfe) {
                u.p("no security file yet. creating an empty map");
                permsMap = new HashMap<String, List<GPerm>>();
            } catch (IOException ex) {
                u.p(ex);
                permsMap = new HashMap<String, List<GPerm>>();
            } catch (SAXException ex) {
                u.p(ex);
                permsMap = new HashMap<String, List<GPerm>>();
            } finally {
            }
        }
        try {
            //u.p("SS: loading up the perms xml");
            XMLDecoder dec = new XMLDecoder(new FileInputStream(Environment.HOME + "/" + STORAGE_FILE));
            permsMap = (Map<String, List<GPerm>>) dec.readObject();
            //u.p("successfully loaded: " + permsMap);
            //u.p("map size = " + permsMap.size());
        } catch (FileNotFoundException fnfe) {
            u.p("no security file yet. creating an empty map");
            permsMap = new HashMap<String, List<GPerm>>();
        } catch (Exception ex) {
            u.p(ex);
            permsMap = new HashMap<String, List<GPerm>>();
        }
    }

    static class TypeSafeEnumPersistenceDelegate extends PersistenceDelegate {

        protected boolean mutatesTo(Object oldInstance, Object newInstance) {
            return oldInstance == newInstance;
        }

        protected Expression instantiate(Object oldInstance, Encoder out) {
            Class type = oldInstance.getClass();
            if (!Modifier.isPublic(type.getModifiers())) {
                throw new IllegalArgumentException("Could not instantiate instance of non-public class: " + oldInstance);
            }
            for (Field field : type.getFields()) {
                int mod = field.getModifiers();
                if (Modifier.isPublic(mod) && Modifier.isStatic(mod) && Modifier.isFinal(mod) && (type == field.getDeclaringClass())) {
                    try {
                        if (oldInstance == field.get(null)) {
                            return new Expression(oldInstance, field, "get", new Object[]{null});
                        }
                    } catch (IllegalAccessException exception) {
                        throw new IllegalArgumentException("Could not get value of the field: " + field, exception);
                    }
                }
            }
            throw new IllegalArgumentException("Could not instantiate value: " + oldInstance);
        }
    }

    public static String unsignedByteToString(byte b) {
        if (b < 0) {
            return "" + (0x000000FF & (int) b);
        }
        return "" + b;
    }

    private static String bytesToIPAddress(InetAddress inetadd) {
        StringBuffer sb = new StringBuffer();
        for (byte b : inetadd.getAddress()) {
            String s = unsignedByteToString(b);
            //p("byte = " + s);
            sb.append(s + ".");
        }
        return sb.toString();
    }

    public static void main(String... args) {
        SecurityStorage storage = SecurityStorage.getInstance();
        SecurityStorage.autosave = true;
        String uuid="myuuid";
        u.p("is granted = " + storage.isGranted(uuid, new SocketPermission("www.weather.gov", "resolve")));
        storage.grantPermissionPermanently(uuid, new GPerm(uuid, "http", "www.weather.gov", GPerm.Type.ALLOW_PERM));
        u.p("is granted = " + storage.isGranted(uuid, new SocketPermission("www.weather.gov", "resolve")));
    }
}